package com.google.code.jbauble.mvc;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.code.jbauble.dao.DatastoreManager;
import com.google.code.jbauble.mvc.support.ActionMapper;
import com.google.code.jbauble.mvc.support.StatusController;
import com.google.code.jbauble.mvc.support.UrlHelper;
import com.google.code.jbauble.mvc.support.WebLoader;

public class DispatcherServlet extends HttpServlet {
	protected final Logger log = LoggerFactory.getLogger(this.getClass());
	protected UrlRouting routing = null;
	protected ViewRenderManager renderManager = null;
	private String encoding = "utf-8";
	
	protected DefaultConfiguration config = null; 
	protected Application app = null;
	protected Filter[] filters = null;
	
	@Override
	public void init()
	          throws ServletException {
		String path = getServletConfig().getServletContext().getRealPath("/");
		System.setProperty(DefaultConfiguration.WEB_PATH, path);
		path = WebLoader.getWebConfigPath(path);
		System.setProperty(DefaultConfiguration.WEB_CONF_PATH, path);
		
		config = new DefaultConfiguration(path + DefaultConfiguration.DEFAULT_CONFIG_FILE);
		encoding = config.getProperty(DefaultConfiguration.SITE_ENCODING);
		
		try {
			DatastoreManager.initDatabase(
					config.getProperty(DefaultConfiguration.SITE_ENV), 
					config.getProperties());
			
			WebContext.init(config);
			
			routing = new UrlRouting(config);
			
			initRouer();
			
			app = createApplcation();
			if (app != null) {
				app.applicationInit();
			}
			
			filters = createFilters();
		} catch (Exception e) {
			log.error("Servlet init error.", e);
			throw new ServletException(e);
		}
		renderManager = new ViewRenderManager(System.getProperty(DefaultConfiguration.WEB_PATH), 
				System.getProperty(DefaultConfiguration.WEB_PATH) 
				+ config.getProperty(DefaultConfiguration.MVC_VIEW_DIR), config);
	}

	private Filter[] createFilters() {
		String str = config.getProperty(DefaultConfiguration.MVC_FILTERS);
		if (str == null || str.equals(""))
			return null;
		String[] ss = str.split(",");
		List<Filter> fs = new ArrayList();
		if (ss == null || ss.length < 1)
			return null;
		for (int i=0; i<ss.length; i++) {
			String s = ss[i].trim();
			if (s.equals(""))
				continue;
			try {
				Filter f = (Filter)Class.forName(s).newInstance();
				if (f == null)
					continue;
				fs.add(f);
			} catch (Exception e) {
				log.error("create filters error." + s, e);
			}
		}
		
		if (fs.size() < 1)
			return null;
		
		Filter[] fa = new Filter[fs.size()];
		fs.toArray(fa);
		return fa;
	}

	private void initRouer() {
		routing.addRoutesFromPackage(config.getProperty(DefaultConfiguration.MVC_PACKAGE));
		
		String env = config.getProperty(DefaultConfiguration.SITE_ENV);
		if (!env.equals("prod")) {
			routing.addRoute("_status/jvm", new StatusController(), "jvm", UrlHelper.HTTP_ALL);
		}
	}

	private Application createApplcation() {
		String className = config.getProperty(DefaultConfiguration.MVC_APPLICATION);
		if (className == null || className.equals(""))
			return null;

		Class c = null;
		try {
			c = Class.forName(className);
			if (!Application.class.isAssignableFrom(c))
				return null;
			Application a = (Application)c.newInstance();
			a.setConfig(config);
			a.setRouter(routing);
			a.setServletConfig(getServletConfig());
			return a;
		} catch (ClassNotFoundException e) {
			log.error("Create applcation error.", e);
		} catch (InstantiationException e) {
			log.error("Create applcation error.", e);
		} catch (IllegalAccessException e) {
			log.error("Create applcation error.", e);
		}
		return null;
	}

	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doServlet(request, response);
	}

	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doServlet(request, response);
	}
	
	@Override
	public void destroy() {
		if (app != null) {
			app.applicationDestroy();
		}
	}
	
	private void doServlet(HttpServletRequest request,
			HttpServletResponse response) throws IOException, ServletException {
		if (filters != null) {
			for (Filter f : filters) {
				if (!f.doFilter(request, response))
					return;
			}
		}
		ActionMapper mapper = routing.getActionMapper(request);
		if (mapper == null) {
			response.setStatus(404);
			log.info(request.getRequestURI() + " is not found.");
			return;
		}
		
		Result result = null;
		try {
			result = mapper.execute(request, response);
		} catch (Exception e) {
			disposeErrorResult(e, request, response);
			log.error("Execute servlet error." + request.getRequestURI(), e);
			throw new ServletException(e);
		}
		
		try {
			disposeResult(mapper, result, request, response);
		} catch (Exception e) {
			disposeErrorResult(e, request, response);
			log.error("Dispose result error." + request.getRequestURI(), e);
			throw new ServletException(e);
		}
	}

	private void disposeErrorResult(Exception e, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
		response.setStatus(500);
	}

	private void disposeResult(ActionMapper mapper, Result result, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		response.setCharacterEncoding(encoding);
		
		Map map = getLayoutParams(mapper.isLayout(), mapper.getLayout());
		renderManager.getRender(result).render(request, response, result, map);
	}

	private Map getLayoutParams(boolean isLayout, String name) {
		if (!isLayout)
			return null;
		Map map = new HashMap();
		map.put("isLayout", isLayout);
		map.put("layout", name);
		return map;
	}
}
